package ldh.im.fxgui.demo;

import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.concurrent.Task;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.text.*;
import javafx.scene.web.HTMLEditor;
import javafx.scene.web.WebView;
import javafx.stage.Popup;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Callback;
import javafx.util.Duration;
import ldh.im.dto.enums.MessageType;
import ldh.im.fxbase.component.BigDataListView;
import ldh.im.fxbase.data.pojo.ImMessage;
import ldh.im.fxbase.data.pojo.ImSession;
import ldh.im.fxbase.data.pojo.ImUser;
import ldh.im.fxbase.data.pojo.status.ImMessageActionStatus;
import ldh.im.fxbase.data.pojo.status.ImMessageStatus;
import ldh.im.fxbase.richtext.ImText;
import ldh.im.fxbase.richtext.ImTextPane;
import ldh.im.fxbase.util.*;
import ldh.im.fxbase.cell.ObjectListCell;
import ldh.im.fxbase.cache.SoftId;
import ldh.im.fxgui.component.ScrollImTextArea;
import ldh.im.fxgui.controller.MessageSessionController;
import ldh.im.fxgui.component.message.MessageVirtualFlowView;
import ldh.im.fxgui.component.message.bo.WrapImMessage;
import ldh.im.fxgui.demo.controller.DemoPaneController;
import ldh.im.fxgui.util.*;
import ldh.im.util.DateUtil;
import org.controlsfx.control.GridCell;
import org.controlsfx.control.GridView;
import org.controlsfx.control.textfield.CustomTextField;
import org.fxmisc.flowless.Cell;
import org.fxmisc.flowless.VirtualFlow;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.*;
import org.kordamp.ikonli.Ikon;
import org.kordamp.ikonli.devicons.Devicons;
import org.kordamp.ikonli.fontawesome.FontAwesome;
import org.kordamp.ikonli.ionicons.Ionicons;
import org.kordamp.ikonli.javafx.FontIcon;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class SampleDemo extends Application {

    private VBox leftPane = new VBox(10);
    private StackPane contentPane = new StackPane();
    private Font font = Font.loadFont(SampleDemo.class.getResource("/font/OpenMoji-Color.ttf").toExternalForm(), 26);
    private Map<String, TitledPane> groupMap = new HashMap();
    private WeakHashMap<Integer, Node> weakMap = new WeakHashMap<>();

    private LocalDateTime now = LocalDateTime.now();

    Map<String, Node> cacheMap = new HashMap<>();

    @Override
    public void start(Stage primaryStage) throws Exception {
        StageUtil.setStage(primaryStage);
        leftPane.getStyleClass().add("demo-left-pane");
        contentPane.getStyleClass().add("demo-content-pane");

        BorderPane borderPane = new BorderPane();
        borderPane.setTop(buildTop());
        ScrollPane leftContainer = new ScrollPane(leftPane);
        leftContainer.setFitToHeight(true);
        leftContainer.setFitToWidth(true);
        borderPane.setLeft(leftContainer);

        ScrollPane contentContainer = new ScrollPane(contentPane);
        contentContainer.setFitToHeight(true);
        contentContainer.setFitToWidth(true);
        borderPane.setCenter(contentContainer);

        buildTests();

        primaryStage.setOnCloseRequest(e->{
            ThreadPoolUtil.close();
            System.exit(0);
        });

        Scene scene = new Scene(borderPane, 800, 600);
//        scene.getStylesheets().add(this.getClass().getResource("/css/init.css").toExternalForm());
        scene.getStylesheets().add(this.getClass().getResource("/css/demo.css").toExternalForm());
//        scene.getStylesheets().add(this.getClass().getResource("/css/var.css").toExternalForm());
//        scene.getStylesheets().add(this.getClass().getResource("/css/main.css").toExternalForm());
//        scene.getStylesheets().add(this.getClass().getResource("/css/tabpane.css").toExternalForm());
//        scene.getStylesheets().add(this.getClass().getResource("/css/window-scrollbar.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.show();

//        ScenicView.show(scene);
    }

    private void buildTests() {
        addTest("listView", "ListView Demo", ()->{
            BigDataListView<Activity> listView = new BigDataListView();
            Image Image = new Image(this.getClass().getResource("/imgs/head.jpg").toExternalForm());
            Callback<Activity, Node> callback = listCell(Image);
            listView.setCellCallback(callback);
            listView.setCache(true);
            listView.getSelectionModel();
            listView.getItems().addAll(loadActivityList(5000));
            return listView;
        });

        addTest("listView","ListView Cache Demo", ()->{
            ListView<Activity> listView = new ListView();
            Image Image = new Image(this.getClass().getResource("/imgs/head.jpg").toExternalForm());
            Callback<Activity, Node> callback = listCell(Image);
            listView.setCellFactory(new ObjectListCell<>(activity -> {
                Node node = weakMap.get(activity.getId());
                if (node != null) {
                    if (weakMap.size() > 100) {
                        cleanWeakMap();
                    }
                    System.out.println("cache node: " + activity.getId() + ", size:" + weakMap.size());
                    return node;
                }
                Node newNode = callback.call(activity);
                weakMap.put(activity.getId(), newNode);
                return newNode;
            }));
            listView.getItems().addAll(loadActivityList(5000));
            return listView;
        });

        addTest("listView","ListView No Cache Demo", ()->{
            ListView<Activity> listView = new ListView();
            Image Image = new Image(this.getClass().getResource("/imgs/head.jpg").toExternalForm());
            Callback<Activity, Node> callback = listCell(Image);
            listView.setCellFactory(new ObjectListCell<>(activity -> {
                Node newNode = callback.call(activity);
                return newNode;
            }));
            listView.getItems().addAll(loadActivityList(5000));
            VBox vBox = new VBox();
            HBox hBox = new HBox();
            Button selectBtn = new Button("select");
            selectBtn.setOnAction(e->{
                int idx = (int) (Math.random() * listView.getItems().size());
                listView.scrollTo(idx);
                listView.getSelectionModel().select(idx);
                selectBtn.setText("select " + idx);
            });
            hBox.getChildren().addAll(selectBtn);
            VBox.setVgrow(listView, Priority.ALWAYS);
            vBox.getChildren().addAll(listView, hBox);
            return vBox;
        });

        addTest("listView","ListView 动态添加数据", ()->{
            ListView<Activity> listView = new ListView();
            Image Image = new Image(this.getClass().getResource("/imgs/head.jpg").toExternalForm());
            Callback<Activity, Node> callback = listCell(Image);
            listView.setCellFactory(new ObjectListCell<>(activity -> {
                Node newNode = callback.call(activity);
                return newNode;
            }));
            int index = 100;
            listView.getItems().addAll(loadActivityList(index));
            VBox vBox = new VBox();

            HBox hBox = new HBox(10);
            Button addDataBtn = new Button("add data");
            AtomicInteger atomicInteger = new AtomicInteger(index++);
            addDataBtn.setOnAction(e->{
                Activity activity = createActivity(atomicInteger.getAndIncrement());
                listView.getItems().add(activity);
                listView.scrollTo(activity);
            });

            Button addDataBtn2 = new Button("add data timer");
            ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
            addDataBtn2.setOnAction(e->{
                scheduledExecutorService.scheduleAtFixedRate(()->{
                    Activity activity = createActivity(atomicInteger.getAndIncrement());
                    FxThreadUtil.runLater(()->{
                        listView.getItems().add(activity);
                        listView.getSelectionModel().select(activity);
                        listView.scrollTo(activity);
                    });
                }, 1000, 100, TimeUnit.MILLISECONDS);
            });

            Button addDataBtn3 = new Button("change value");
            addDataBtn3.setOnAction(e->{
                int idx = (int) (Math.random() * listView.getItems().size());
                listView.scrollTo(idx);
                listView.getSelectionModel().select(idx);

            });
            hBox.getChildren().addAll(addDataBtn, addDataBtn2, addDataBtn3);
            VBox.setVgrow(listView, Priority.ALWAYS);
            vBox.getChildren().addAll(listView, hBox);
            return vBox;
        });

        addTest("listView","Flowless 动态添加数据", ()->{
            VBox vBox = new VBox();
            int index = 100;
            BooleanProperty canSelectProperty = new SimpleBooleanProperty(false);
            List<Activity> activities = loadActivityList(index);
            ObservableList<Activity> items = FXCollections.observableList(activities);
            Callback<Activity, Node> callback = listCell(canSelectProperty);
            VirtualFlow<Activity, ?> flow = VirtualFlow.createVertical(
                    items,
                    aitivity -> {
                        Node node = callback.call(aitivity);
                        return Cell.wrapNode(node);
                    });
            VirtualizedScrollPane stackPane = new VirtualizedScrollPane(flow);

            HBox hbox = new HBox(10);
            Button scroll = new Button("scroll");
            scroll.setOnAction(e->{
                int t = (int)(Math.random() * activities.size());
                scroll.setText("scroll to " + t);
                flow.show(t);
            });

            Button lasted = new Button("lasted");
            lasted.setOnAction(e->{
                flow.show(activities.size());
            });

            Button addDataBtn = new Button("add data");
            AtomicInteger atomicInteger = new AtomicInteger(index++);
            addDataBtn.setOnAction(e->{
                int idx = atomicInteger.getAndIncrement();
                Activity activity = createActivity(idx);
                items.add(activity);
                flow.show(idx-1);
            });

            Button addDataBtn2 = new Button("add data timer");
            ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
            addDataBtn2.setOnAction(e->{
                scheduledExecutorService.scheduleAtFixedRate(()->{
                    boolean isShow = addDataBtn2.getScene() != null && StageUtil.getStage().getScene().getWindow() == addDataBtn2.getScene().getWindow();
                    if (!isShow) {
                        scheduledExecutorService.shutdownNow();
                        System.out.println("Flowless 动态添加数据 shutdown");
                        return;
                    }
                    int idx = atomicInteger.getAndIncrement();
                    Activity activity = createActivity(atomicInteger.getAndIncrement());
                    FxThreadUtil.runLater(()->{
                        items.add(activity);
                        flow.show(idx-1);
                    });
                }, 1000, 100, TimeUnit.MILLISECONDS);
            });

            Button changeDataBtn3 = new Button("change value");
            changeDataBtn3.setOnAction(e->{
                int t = (int)(Math.random() * activities.size());
                scroll.setText("scroll to " + t);
                Activity activity = items.get(t);
                flow.show(t);
                activity.setSelectProperty(!activity.selectProperty.getValue());
            });

            Button canSelectDataBtn4 = new Button("can select");
            canSelectDataBtn4.setOnAction(e->{
                canSelectProperty.setValue(!canSelectProperty.getValue());
            });

            hbox.getChildren().addAll(scroll, lasted, addDataBtn, addDataBtn2, changeDataBtn3, canSelectDataBtn4);
            vBox.getChildren().addAll(hbox, stackPane);
            VBox.setVgrow(stackPane, Priority.ALWAYS);
            return vBox;
        });

        addTest("listView","Flowless 动态添加ImMessage数据", ()->{
            VBox vBox = new VBox();
            int index = 100;
            ImUser loginImUser = new ImUser();
            loginImUser.setUserId("test00200");
            UiUtil.setLoginImUser(loginImUser);

            ImUser imUser1 = new ImUser();
            imUser1.setUserId("test00100");
            imUser1.setUserName("test00100");
            ImCacheFactory.getInstance().putUser("test00100", imUser1);
            ImSession imSession = new ImSession();
            imSession.setId(1L);

            MessageVirtualFlowView sendMessageHandler = new MessageVirtualFlowView(null, null);
            sendMessageHandler.setImSession(imSession);

            AtomicInteger aindx = new AtomicInteger(index + 1);
            sendMessageHandler.setLoadLastCallback(handle->{
                int start = sendMessageHandler.getItems().get(sendMessageHandler.getItems().size() - 1).getImMessage().getId().intValue();
                aindx.set(start);
                List<WrapImMessage> wrapImMessages = IntStream.range(0, 10).mapToObj(n->{
                    int id = aindx.incrementAndGet();
                    ImMessage message = createImMessage(id);
                    return WrapImMessage.wrap(message);
                }).collect(Collectors.toList());
                sleep(2000);
                return wrapImMessages;
            });

            sendMessageHandler.setLoadFrontCallback(handle->{
                int start = sendMessageHandler.getItems().get(0).getImMessage().getId().intValue();
                aindx.set(start);
                List<WrapImMessage> wrapImMessages = IntStream.range(0, 10).mapToObj(n->{
                    int id = aindx.decrementAndGet();
                    ImMessage message = createImMessage(id);
                    return WrapImMessage.wrap(message);
                }).collect(Collectors.toList());
                sleep(2000);
                return wrapImMessages;
            });


            UiUtil.setSendMessageHandler(sendMessageHandler);

            Node pane = sendMessageHandler.createMessagePaneNode();

            List<WrapImMessage> messages = loadImMessageList(index);
            sendMessageHandler.addLasts(messages);

            HBox hbox = new HBox(10);

            Button addDataBtn = new Button("add data");
            AtomicInteger atomicInteger = new AtomicInteger(index++);
            addDataBtn.setOnAction(e->{
                int idx = atomicInteger.getAndIncrement();
                ImMessage message = createImMessage(idx);
                sendMessageHandler.addMessage(message);
            });

            Button addDataBtn2 = new Button("add data timer");
            ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
            addDataBtn2.setOnAction(e->{
                scheduledExecutorService.scheduleAtFixedRate(()->{
                    boolean isShow = addDataBtn2.getScene() != null && StageUtil.getStage().getScene().getWindow() == addDataBtn2.getScene().getWindow();
                    if (!isShow) {
                        scheduledExecutorService.shutdownNow();
                        System.out.println("Flowless 动态添加ImMessage数据 shutdown");
                        return;
                    }
                    int idx = atomicInteger.getAndIncrement();
                    ImMessage message = createImMessage(idx);
                    FxThreadUtil.runLater(()->{
//                        sendMessageHandler.addMessage(message);
                        UiUtil.uiShowMessage(message);
                    });
                }, 1000, 50, TimeUnit.MILLISECONDS);
            });

            hbox.getChildren().addAll(addDataBtn, addDataBtn2);
            vBox.getChildren().addAll(hbox, pane);
            VBox.setVgrow(pane, Priority.ALWAYS);
            return vBox;
        });

        addTest("listView","Flowless Demo", ()->{
            VBox vBox = new VBox();
            List<Activity> activities = loadActivityList(5000);
            ObservableList<Activity> items = FXCollections.observableList(activities);
            Image Image = new Image(this.getClass().getResource("/imgs/head.jpg").toExternalForm());
            Callback<Activity, Node> callback = listCell(Image);
            VirtualFlow<Activity, ?> flow = VirtualFlow.createVertical(
                    items,
                    aitivity -> {
                        Node node = callback.call(aitivity);
                        return Cell.wrapNode(node);
                    });
            VirtualizedScrollPane stackPane = new VirtualizedScrollPane(flow);

            HBox hbox = new HBox();
            Button scroll = new Button("scroll");
            scroll.setOnAction(e->{
                int t = (int)(Math.random() * activities.size());
                scroll.setText("scroll to " + t);
                flow.show(t);
            });

            Button lasted = new Button("lasted");
            lasted.setOnAction(e->{
                flow.show(activities.size());
            });

            hbox.getChildren().addAll(scroll, lasted);
            vBox.getChildren().addAll(hbox, stackPane);
            VBox.setVgrow(stackPane, Priority.ALWAYS);
            return vBox;
        });

        addTest("textArea","HTMLEditor Demo", ()->{
            Button b = new Button("add image");
            HTMLEditor textArea = HtmlEditorUtil.createHtmlEditor();
            Label l = new Label(); l.setText("<h2>asfdasfa</h2>");
            VBox vBox = new VBox(b, textArea, l);
            b.setOnAction(e->{
                String t = "file://F:\\other\\aaa\\22.png";
                String tt = textArea.getHtmlText();
                String txt = tt + String.format("<img src=\"%s\"/>", t);
                System.out.println("txt:" + txt);
                textArea.setHtmlText(txt);
            });

            return vBox;
        });

        addTest("textArea","HTMLEditor Demo", ()->{
            VBox content = new VBox();

            VBox contentPanet = new VBox();
            ScrollPane scrollPane = new ScrollPane();
            scrollPane.setContent(contentPanet);
            VBox.setVgrow(scrollPane, Priority.ALWAYS);

            HBox hBox = new HBox(5);
            Button b = new Button("add");
            HTMLEditor textArea = new HTMLEditor();
            textArea.setPrefHeight(200);

            hBox.getChildren().addAll(textArea, b);

            b.setOnAction(e->{
                HTMLEditor htmlEditor = HtmlEditorUtil.createHtmlEditor();
                String txt = textArea.getHtmlText();
                htmlEditor.setHtmlText(txt);
                htmlEditor.setPrefWidth(300);
                htmlEditor.setPrefHeight(50);
                double h = RegionUtil.calcStringHeight(txt, 300);
                htmlEditor.setPrefHeight(h-20);
                contentPanet.getChildren().add(htmlEditor);

            });

            content.getChildren().addAll(scrollPane, hBox);
            return content;
        });

        addTest("textArea","HtmlEditor Demo2 height", ()->{
            long start = System.currentTimeMillis();
            VBox contentPanet = new VBox();
            ScrollPane scrollPane = new ScrollPane();
            scrollPane.setContent(contentPanet);
            String str ="I have a HTML5 UI and a Java backend and want to avoid rebuilding the HTML ui in plain java, so my idea was to run" +
                    " a local webserver and use a webview to render it in a \"native\" window. The solution seems to be to use a JavaFX WebView " +
                    "which can be embedded in swing. On the paper it sounds great (especially since they claim to use WebKit which has a " +
                    "significantly better performance for my UI in Chrome / Safari).";
            for(int i=0; i<300; i++) {
                HTMLEditor htmlEditor = HtmlEditorUtil.createHtmlEditor();
                htmlEditor.setHtmlText(str);
                htmlEditor.setPrefSize(400, 100);

                Text textHolder = new Text();
                textHolder.setWrappingWidth(400);
//                double h2 = RegionUtil.calcStringHeight(str, 400);
                contentPanet.getChildren().add(htmlEditor);
                textHolder.setText(str);
                double h = textHolder.layoutBoundsProperty().get().getHeight();
                System.out.println("h11:" + h);
//                textHolder.layoutBoundsProperty().addListener((ob, o, newValue)->{
//                    double h = textHolder.getLayoutBounds().getHeight() + 10;
//                    htmlEditor.setPrefHeight(h);
//                    System.out.print("h:" + h);
//                });
                htmlEditor.setPrefHeight(h);
            }
            System.out.println("htmlEdit time:" + (System.currentTimeMillis() - start));
            return scrollPane;
        });

        addTest("textArea","RichTextArea Demo", ()->{
            long start = System.currentTimeMillis();
            VBox contentPanet = new VBox(5);
            ScrollPane scrollPane = new ScrollPane();
            scrollPane.setContent(contentPanet);
            String str ="I have a HTML5 UI and a Java backend and want to avoid rebuilding the HTML ui in plain java, so my idea was to run" +
                    " a local webserver and use a webview to render it in a \"native\" window. The solution seems to be to use a JavaFX WebView " +
                    "which can be embedded in swing. On the paper it sounds great (especially since they claim to use WebKit which has a " +
                    "significantly better performance for my UI in Chrome / Safari).";
            for(int i=0; i<300; i++) {
                CodeArea codeArea = new CodeArea();
                codeArea.setEditable(true);
                codeArea.setWrapText(true);
                codeArea.replaceText(0, 0, str);
                codeArea.setPrefWidth(400);
                contentPanet.getChildren().add(codeArea);
            }
            System.out.println("JEditorPane time:" + (System.currentTimeMillis() - start));
            return scrollPane;
        });

        addTest("textArea","WebView Demo", ()->{
            long start = System.currentTimeMillis();
            VBox contentPanet = new VBox(5);
            ScrollPane scrollPane = new ScrollPane();
            scrollPane.setContent(contentPanet);
            String str ="I have a HTML5 UI and a Java backend and want to avoid rebuilding the HTML ui in plain java, so my idea was to run" +
                    " a local webserver and use a webview to render it in a \"native\" window. The solution seems to be to use a JavaFX WebView " +
                    "which can be embedded in swing. On the paper it sounds great (especially since they claim to use WebKit which has a " +
                    "significantly better performance for my UI in Chrome / Safari).";
            for(int i=0; i<300; i++) {
                WebView webView = new WebView();
                webView.setPrefWidth(400);
                webView.setPrefHeight(100);
                webView.getEngine().loadContent(str);
                contentPanet.getChildren().add(webView);
            }
            System.out.println("WebView time:" + (System.currentTimeMillis() - start));
            return scrollPane;
        });

        addTest("textArea","TextArea Demo", ()->{
            long start = System.currentTimeMillis();
            VBox contentPanet = new VBox(5);
            ScrollPane scrollPane = new ScrollPane();
            scrollPane.setContent(contentPanet);
            String str ="I have a HTML5 UI and a Java backend and want to avoid rebuilding the HTML ui in plain java, so my idea was to run" +
                    " a local webserver and use a webview to render it in a \"native\" window. The solution seems to be to use a JavaFX WebView " +
                    "which can be embedded in swing. On the paper it sounds great (especially since they claim to use WebKit which has a " +
                    "significantly better performance for my UI in Chrome / Safari).";

            for(int i=0; i<300; i++) {
                Text text = new Text(str);
                text.setFont(Font.font(21));
                text.setWrappingWidth(400);
                double height = text.getBoundsInLocal().getHeight();
                TextArea textArea = new TextArea(str);
                textArea.setWrapText(true);
                textArea.setPrefSize(400, height);
                contentPanet.getChildren
                        ().add(textArea);
            }
            System.out.println("TextArea time:" + (System.currentTimeMillis() - start));
            return scrollPane;
        });

        addTest("textArea","textArea height", ()->{
            Pane root = new Pane();

             ScrollPane scroller;
             Pane content = new Pane();

             TextField textF = new TextField();
             TextArea textA = new TextArea();

             Text textHolder = new Text();
            content.setMinSize(300, 300);
            textF.setPrefSize(260, 40);
            textF.setLayoutX(20);
            textF.setLayoutY(20);
            textA.setPrefSize(260, 200);
            textA.setLayoutX(20);
            textA.setLayoutY(80);

//            textA.getStylesheets().add(getClass().getResource("texta.css").toExternalForm());
            content.getChildren().addAll(textA,textF);

            /*************************@Uluk Biy Code**************************/
            textA.setWrapText(true);
            textHolder.textProperty().bind(textA.textProperty());
            textHolder.layoutBoundsProperty().addListener((ob, o, newValue)->{
                textA.setPrefHeight(textHolder.getLayoutBounds().getHeight() + 10);
            });
            /****************************************************************/

            scroller = new ScrollPane(content);
            scroller.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
            scroller.setPrefSize(300, 316);

            return scroller;
        });

        addTest("textArea","TextField Font Demo", ()->{
            VBox vBox = new VBox(10);
            TextField textField = new TextField("Look! That's a coloured emoji! \uD83D\uDE04");
            vBox.getChildren().addAll(textField, new Label("Look! That's a coloured emoji! \uD83D\uDE04"));

            Text text = new Text("Box with long description that should be wrapped");
            text.textProperty().bind(textField.textProperty());
            text.setCaretPosition(5);
            TextFlow textFlow = new TextFlow(text) {
                @Override
                protected void layoutChildren() {
                    super.layoutChildren();

                    double maxChildWidth = 0;
                    for (Node child : getManagedChildren()) {
                        double childWidth = child.getLayoutBounds().getWidth();
                        maxChildWidth = Math.max(maxChildWidth, childWidth);
                    }
                    double insetWidth = getInsets().getLeft() + getInsets().getRight();
                    double adjustedWidth = maxChildWidth + insetWidth;

                    setMaxWidth(adjustedWidth);
                }
            };

            textFlow.setStyle("-fx-background-color: red");
            textFlow.setMaxWidth(Control.USE_PREF_SIZE);
            textFlow.setMaxHeight(Control.USE_PREF_SIZE);

            Button unclamp = new Button("Unclamp max width");
            unclamp.setOnAction(e -> textFlow.setMaxWidth(Double.MAX_VALUE));

            StackPane wrappedText = new StackPane(textFlow);

            VBox.setVgrow(wrappedText, Priority.ALWAYS);
            vBox.getChildren().addAll(unclamp, wrappedText);
            return vBox;
        });

        addTest("textArea","TextFlow Demo", ()->{
            ScrollPane scrollPane = new ScrollPane();
            String str ="I have a HTML5 UI and a Java backend and want to avoid rebuilding the HTML ui in plain java, so my idea was to run" +
                    " a local webserver and use a webview to render it in a \"native\" window. The solution seems to be to use a JavaFX WebView " +
                    "which can be embedded in swing. On the paper it sounds great (especially since they claim to use WebKit which has a " +
                    "significantly better performance\uD83D\uDC68 for my UI in Chrome \uD83D\uDC68\u200D\uD83D\uDCBB / Safari)." +
                    "ABC \uD83D\uDC68\u200D\uD83D\uDCBB \uD83E\uDD76  XYZ \uD83D\uDCBB \uD83D\uDC68 \uD83E\uDDDF Lorem \uE380  ipsum \uD83D\uDCAF";
            Text t = buildText(null);
            t.setText(str);
            t.setSelectionFill(Color.RED);
//            t.setWrappingWidth(400);

            Text t2 = buildText(t);
            t2.setText(str);
            t2.setSelectionFill(Color.RED);
//            t2.setWrappingWidth(400);
            Label label = new Label("\uD83D\uDC31");
            label.setStyle("-fx-text-fill: red; -fx-fill: red");
            TextFlow textFlow = new TextFlow(t, t2, label);
            StackPane stackPane = new StackPane(textFlow);

            textFlow.setOnDragDetected(e->{
                System.out.println(String.format("x:%s, y:%s", e.getX(), e.getY()));
                HitInfo hitInfo = textFlow.hitTest(new Point2D(e.getX(), e.getY()));
                t.setCaretPosition(hitInfo.getCharIndex());
                t.setSelectionStart(hitInfo.getCharIndex());
                t.setSelectionEnd(hitInfo.getCharIndex());
                t2.setSelectionStart(hitInfo.getCharIndex());


            });
            textFlow.setOnMouseDragged(e->{
                HitInfo hitInfo = textFlow.hitTest(new Point2D(e.getX(), e.getY()));
                t.setSelectionEnd(hitInfo.getCharIndex());
                t2.setSelectionEnd(hitInfo.getCharIndex());

                PathElement[] pathElements = textFlow.caretShape(hitInfo.getCharIndex(), true);
                Path path = new Path(pathElements);
                stackPane.getChildren().clear();
                stackPane.getChildren().addAll(path, textFlow);
            });

            textFlow.focusedProperty().addListener((InvalidationListener)l->{
                if (!textFlow.isFocused()) {
                    t.setSelectionStart(0);
                    t.setSelectionEnd(0);

                    t2.setSelectionStart(0);
                    t2.setSelectionEnd(0);
                }
            });

            scrollPane.setFitToWidth(true);
            scrollPane.setContent(stackPane);
            return scrollPane;
        });

        addTest("textArea","TextArea Emoji Demo", ()->{
            TextArea textArea = new TextArea();
            textArea.setPrefHeight(60);
            textArea.setPrefRowCount(5);
            textArea.setStyle("-fx-fill: yellow");

            TextArea textArea1 = new TextArea("\uD83D\uDC31");
            Label label = new Label("ABC \uD83D\uDC68\u200D\uD83D\uDCBB &#x1F976;  XYZ #x1F4BB; \uD83D\uDC31;\uD83D\uDC44");
            textArea1.setFont(font);
            label.setFont(font);

            Button e3 = new Button();
            e3.setText("\uD83D\uDC44");
            e3.setStyle("-fx-text-fill: #ff0000;");

            CodeArea codeArea = new CodeArea();
            codeArea.setWrapText(true);
            codeArea.setEditable(true);
            codeArea.insertText(0, "ABC \uD83D\uDC68\u200D\uD83D\uDCBB &#x1F976;  XYZ #x1F4BB; \uD83D\uDC31;\uD83D\uDC44");

            Text text = new Text();
            text.setWrappingWidth(400);
            text.setFont(font);
            text.setText("ABC \uD83D\uDC68\u200D\uD83D\uDCBB &#x1F976;  XYZ #x1F4BB; \uD83D\uDC31;\uD83D\uDC44");

            VBox vBox = new VBox(10, textArea, textArea1, label, e3, codeArea, text);
            ScrollPane scrollPane = new ScrollPane(vBox);
            scrollPane.setFitToWidth(true);
            scrollPane.setFitToHeight(true);
            return scrollPane;
        });

        addTest("textArea","WebView Demo", ()->{
            VBox vBox = new VBox(10);
            WebView webView = new WebView();

            final String cssLink = MessageSessionController.class.getResource("/css/html.css").toExternalForm();
            final String jsLink = MessageSessionController.class.getResource("/js/html.js").toExternalForm();
            final String initHtml = "<link rel=\"stylesheet\" href=\"" + cssLink +"\" /><br/><script type=\"text/javascript\" src=\"" + jsLink + "\"></script>";

            String styleAndJs = initHtml;
            String initHtmls=String.format("<html dir=\"ltr\"><head>%s</head><body contenteditable=\"true\"><div id=\"content\"/></body></html>", styleAndJs);
            webView.getEngine().loadContent(initHtmls);
            webView.getEngine().setJavaScriptEnabled(true);

            Button b = new Button("add img");
            b.setOnAction(e->{
                String img = MessageSessionController.class.getResource("/imgs/head.jpg").toExternalForm();
                String tt = String.format("<img src='%s' class='emoji'/>", img) ;

                try {
                    webView.getEngine().executeScript("insertHtml(\"" + tt + "\")");
                } catch (Exception js) {
                    String t = HtmlEditorUtil.getWebViewHtml(webView);
                }
            });

            Button b5 = new Button("add icon");
            b5.setOnAction(e->{
                UiUtil.getEmojiPane().show(b5, emojiSpan->{
                    HtmlEditorUtil.insertHtml(webView, emojiSpan);
                });
            });

            Button b2 = new Button("get data");
            b2.setOnAction(e->{
                String html = (String) webView.getEngine().executeScript("document.documentElement.outerHTML");
                System.out.println("text:" + webView.getEngine().getDocument().getTextContent());
                System.out.println("string:" + html);
            });
            Button b3 = new Button("add cart");
            b3.setOnAction(e->{
                webView.getEngine().executeScript("placeCaretAtEnd(document.getElementById(\"content\"))");
            });
            HBox hBox = new HBox(10, b, b5, b2, b3);
            vBox.getChildren().addAll(webView, hBox);
            return vBox;
        });

        addTest("textArea","HtmlEditor Demo", ()->{
            VBox vBox = new VBox(10);
            HTMLEditor webView = new HTMLEditor();

            Button b2 = new Button("get data");
            b2.setOnAction(e->{
                String html = webView.getHtmlText();
                System.out.println("string:" + html);
            });

            Button b3 = new Button("add HTMLEditor");
            b3.setOnAction(e->{
                ThreadPoolUtil.submit(()->{
                    HTMLEditor webView2 = new HTMLEditor();
                    FxThreadUtil.runLater(()->vBox.getChildren().add(webView2));
                });
            });
            vBox.getChildren().addAll(webView, b2, b3);
            return vBox;
        });

        addTest("textArea","WebView load Demo", ()->{
            VBox vBox = new VBox(10);
            WebView webView = new WebView();
            String url = "F:/download/openmoji-font/demo.html";
            webView.getEngine().load("file://" +url);

            Button b2 = new Button("get data");
            b2.setOnAction(e->{
                String html = HtmlEditorUtil.getWebViewHtml(webView);
                System.out.println("string:" + html);
            });
            HTMLEditor htmlEditor = HtmlEditorUtil.createHtmlEditor();
            try {
                String t = Files.readString(Paths.get(url));
                System.out.println("t:" + t);
                htmlEditor.setHtmlText(t);
            } catch (Exception e) {
                e.printStackTrace();
            }

            vBox.getChildren().addAll(webView, b2, htmlEditor);
            return vBox;
        });

        addTest("textArea","Capture Screen Demo", ()->{
            VBox vBox = new VBox(10);
            WebView webView = new WebView();
            webView.getEngine().loadContent(UiUtil.getInitWebViewHtml());

            Button b1 = new Button("截屏111");
            b1.setOnAction(e->{
                UiUtil.getScreenCapturePane().show(str->{
                    HtmlEditorUtil.insertHtml(webView, str);
                });
            });

            Button b2 = new Button("获取数据");
            b2.setOnAction(e->{
                System.out.println(HtmlEditorUtil.getWebViewHtml(webView));
            });

            vBox.getChildren().addAll(webView, b1, b2);
            webView.getEngine().loadContent(UiUtil.getInitWebViewHtml());
            return vBox;
        });

        addTest("textArea","New Stage Demo", ()->{
            VBox vBox = new VBox();
            Button b1 = new Button("New Stage");
            Button b2 = new Button("截屏111");
            b2.setOnAction(e1->{
                UiUtil.getScreenCapturePane().show(str->{
                    System.out.println(str);
//                        HtmlEditorUtil.insertHtml(webView, str);
                });
            });

            b1.setOnAction(e->{
                Stage stage = new Stage();
                stage.initStyle(StageStyle.TRANSPARENT);
                Label label = new Label();
                Button b3 = new Button("截屏111");
                b3.setOnAction(e1->{
                    UiUtil.getScreenCapturePane().show(str->{
                        System.out.println(str);
//                        HtmlEditorUtil.insertHtml(webView, str);
                    });
                });
                HBox hBox = new HBox(label, b3);
                StackPane stackPane = new StackPane(hBox);
//                Scene scene = new Scene(stackPane, Screen.getPrimary().getBounds().getWidth(), Screen.getPrimary().getBounds().getHeight());
                Scene scene = new Scene(stackPane);
                stage.setScene(scene);
                stackPane.setOnMousePressed(ee->{
                    String d = String.format("x:%s, y:%s, w:%s, h:%s", ee.getX(), ee.getY(), stackPane.getWidth(), stackPane.getHeight());
                    label.setText(d);
                    Screen screen = Screen.getPrimary();
                    String t = String.format("w: %s, h:%s", screen.getBounds().getWidth(), screen.getBounds().getHeight());
                    System.out.println("screen size: " + t);
                });
                stackPane.setOnMouseClicked(ee->{
                    if (ee.getClickCount() == 2) stage.close();
                });
                stage.setFullScreen(true);
                stage.show();

            });

            vBox.getChildren().addAll(b1, b2);
            return vBox;
        });

        addTest("textArea","TextFlow test", ()->{
            TextFlow textFlow = new TextFlow(
                    new Text("Box with long description that should be wrapped"),
                    new Text("Disconnected from the target VM, address: '127.0.0.1:49311', <br/>transport: 'socket'"),
                    new Text("Disconnected from the target VM, address: '127.0.0.1:49311',<br/> transport: 'socket'"),
                    new Text("Disconnected from the target VM, address: '127.0.0.1:49311', <br/>transport: 'socket'"),
                    new Text("Disconnected from the target VM, address: '127.0.0.1:49311',<br/> transport: 'socket'"),
                    new Text("Connected to the target VM, address: '127.0.0.1:49311', <br/>transport: 'socket'")
            ) {
                @Override
                protected void layoutChildren() {
                    super.layoutChildren();

                    double maxChildWidth = 0;
                    for (Node child : getManagedChildren()) {
                        double childWidth = child.getLayoutBounds().getWidth();
                        maxChildWidth = Math.max(maxChildWidth, childWidth);
                    }
                    double insetWidth = getInsets().getLeft() + getInsets().getRight();
                    double adjustedWidth = maxChildWidth + insetWidth;

                    setMaxWidth(adjustedWidth);
                }
            };

            textFlow.setStyle("-fx-background-color: red");
            textFlow.setMaxWidth(Control.USE_PREF_SIZE);
            textFlow.setMaxHeight(Control.USE_PREF_SIZE);

            Button unclamp = new Button("Unclamp max width");
            unclamp.setOnAction(e -> textFlow.setMaxWidth(Double.MAX_VALUE));

            StackPane wrappedText = new StackPane(textFlow);

            VBox vbox = new VBox(
                    unclamp,
                    wrappedText
            );
            VBox.setVgrow(wrappedText, Priority.ALWAYS);
            return vbox;
        });

        addRichTest();

        addIcons();

        addPath();

        addCheckBoxes();

        addTest();

    }

    private void addRichTest() {
        addTest("richTextfx", "richTextFx Demo", ()->{
            VBox vBox = new VBox();
            final InlineCssTextArea area = new InlineCssTextArea();
            String alphabet = "abcdefghijklmnopqrstuvwxyz";
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 10; i++) {
                sb.append(i).append(" :").append(alphabet).append("\n");
            }
            area.appendText(sb.toString());
            area.setWrapText(true);
            VirtualizedScrollPane virtualizedScrollPane = new VirtualizedScrollPane(area);

            setupRTFXSpecificCSSShapes(area);

            addExtraCaret(area);

            addExtraSelection(area);

            // select some other range with the regular caret/selection before showing area
            area.selectRange(2, 0, 2, 4);

//            area.setEditable(false);

            area.setOnKeyReleased(e->{
                String c = e.getText();
                area.appendText(c);
            });
            area.requestFocus();
            area.setEditable(false);
            area.append("hello", "-fx-fill: red");
            Button button = new Button("add");
            vBox.getChildren().addAll(virtualizedScrollPane, button);
            VBox.setVgrow(virtualizedScrollPane, Priority.ALWAYS);
            button.setOnAction(e->{
                area.appendText("sfafdafafafasfsadfal22$$$$$$$$$$$$$$$$$$$$$$$$$$$$qqqqqqqqqqqqqqqqqqqqqq" +
                        "sdafas asdfaf sfafdafafafasfsadfal22$$$$$$$$$$$$$$$$$$$$$$$$$$$$qqqqqqqqqqqqqqqqqqqqqqsafd");
                area.moveTo(area.getLength());
                area.showParagraphInViewport(area.getParagraphs().size());
                area.showParagraphAtBottom(area.getParagraphs().size());
                area.requestFocus();
            });
            return vBox;
        });

        addTest("richTextfx", "java Demo", ()->{
            InlineCssTextArea area = new InlineCssTextArea();
            String alphabet = "abcdefghijklmnopqrstuvwxyz";
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 10; i++) {
                sb.append(i).append(" :").append(alphabet).append("\n");
            }
            area = new InlineCssTextArea(sb.toString());

            setupRTFXSpecificCSSShapes(area);

            addExtraCaret(area);

            addExtraSelection(area);

            // select some other range with the regular caret/selection before showing area
            area.selectRange(2, 0, 2, 4);

            area.requestFocus();
            return area;
        });

        addTest("richTextfx", "svg Demo", ()->{

            ImageView imageView = new ImageView(this.getClass().getResource("/im-svg/no_read.svg").toExternalForm());
            return imageView;
        });
    }

    private void addIcons() {
        addTest("icons", "icons", ()->{
            ObservableList<Label> items = FXCollections.observableArrayList();
            FilteredList<Label> filteredList = new FilteredList<>(items, s->true);

            VBox vBox = new VBox(10);
            CustomTextField searchTextField = new CustomTextField();
            Label searchLabel = new Label("查询");
            HBox hBox = new HBox(20);
            hBox.getChildren().addAll(searchLabel, searchTextField);

            searchTextField.textProperty().addListener(l->{
                String searchText = searchTextField.getText().trim();
                filteredList.setPredicate(s->s.getText().contains(searchText));
            });

            GridView<Label> gridView = new GridView<>();
            gridView.setCellHeight(80);
            gridView.setCellWidth(80);
            gridView.setCellFactory(grid->new GridCell<Label>() {

                @Override
                protected void updateItem(Label label, boolean empty) {
                    super.updateItem(label, empty);
                    if (empty || label == null) {
                        setGraphic(null);
                    } else {
                        setGraphic(label);
                    }
                }
            });
            gridView.setItems(filteredList);

            addItems(items, FontAwesome.values(), s->FontAwesome.findByDescription(s));
            addItems(items, Devicons.values(), s->Devicons.findByDescription(s));
            addItems(items, Ionicons.values(), s->Ionicons.findByDescription(s));

            vBox.getChildren().addAll(searchTextField, gridView);
            return vBox;
        });
    }

    private void addPath() {
        addTest("icons", "path demo", ()->{
            VBox vBox = new VBox(10);
            Path path = new Path();
            MoveTo moveTo = new MoveTo(50, 50);
            HLineTo hline = new HLineTo(200);
            VLineTo vline = new VLineTo(200);
            HLineTo hline2 = new HLineTo(50);
            VLineTo vline2 = new VLineTo(50);

            MoveTo moveTo3 = new MoveTo(80, 80);
            HLineTo hline3 = new HLineTo(170);
            VLineTo vline3 = new VLineTo(170);
            HLineTo hline31 = new HLineTo(80);
            VLineTo vline31 = new VLineTo(80);
            path.getElements().addAll(moveTo, hline, vline, hline2, vline2, moveTo3, hline3, vline3, hline31, vline31);
            path.setFill(Color.BEIGE);
            vBox.getChildren().addAll(path);
            return vBox;
        });
    }

    private void addCheckBoxes() {
        addTest("icons", "checkBox demo", ()->{
            VBox vBox = new VBox(10);
            ComboBox<String> userNameComboBox = new ComboBox<>();
            userNameComboBox.setEditable(true);
            userNameComboBox.getItems().addAll("test1", "test2", "test3", "test4");

            PasswordField passwordField = new PasswordField();
            Button b1 = new Button("b1");
            vBox.getChildren().addAll(userNameComboBox, passwordField, b1);
            return vBox;
        });
    }

    private void addTest() {
        addTest("test", "task demo", ()->{
            VBox vBox = new VBox(10);
            Button b = new Button("do long time task!");
            Label ml = new Label();
            Label l = new Label();
            b.setOnAction(e->{
                Task<String> task = new Task(){

                    @Override
                    protected Object call() throws Exception {
                        Thread.sleep(1000);
                        int n = (int) (Math.random() * 100);
                        if (n > 50) {
                            throw new Exception("fff");
                        }
                        return "success";
                    }
                };
                ThreadPoolUtil.submitTask(task);
                l.textProperty().bind(task.valueProperty());
                task.setOnSucceeded(s->{
                    System.out.println("succes!!!");
                });
                task.setOnFailed(ee->{
                    System.out.println("fail!!!");
                    task.getException().printStackTrace();
                });
            });
            vBox.getChildren().addAll(b, l);
            return vBox;
        });

        addTest("test", "controler 销毁 demo", ()->{
            BorderPane borderPane = new BorderPane();
            VBox leftPane = new VBox(10);
            ObjectProperty<DemoPaneController> currentDemoControllerProperty = new SimpleObjectProperty<>();
            for (int i=0; i<10; i++) {
                Button button = new Button("add control " + i);
                int idx = i;
                button.setOnAction(e->{
                    RegionUtil.FxmlBean<Pane, DemoPaneController> fxmlBean = RegionUtil.loadFxmlBean(this.getClass().getResource("/demo/DemoPane.fxml"));
                    fxmlBean.getController().setName("demo " + idx);
                    borderPane.setCenter(fxmlBean.getNode());
                    currentDemoControllerProperty.set(fxmlBean.getController());
                });
                leftPane.getChildren().add(button);
            }
            currentDemoControllerProperty.addListener((l, o, n)->{
                if (o != null) {
                    o.close();
                }

            });
            borderPane.setLeft(leftPane);
            return borderPane;
        });

        addTest("test", "scroll imTextArea", ()->{
            BorderPane borderPane = new BorderPane();
            VBox leftPane = new VBox(10);
            ObjectProperty<DemoPaneController> currentDemoControllerProperty = new SimpleObjectProperty<>();
            for (int i=0; i<10; i++) {
                Button button = new Button("add control " + i);
                int idx = i;
                button.setOnAction(e->{
                    ScrollImTextArea scrollImTextArea = new ScrollImTextArea();
                    borderPane.setCenter(scrollImTextArea);
                });
                leftPane.getChildren().add(button);
            }
            currentDemoControllerProperty.addListener((l, o, n)->{
                if (o != null) {
                    o.close();
                }

            });
            borderPane.setLeft(leftPane);
            return borderPane;
        });

        addTest("test", "dropshadow", ()->{
            VBox vBox = new VBox();
            Popup popup = new Popup();
            popup.setAutoHide(true);

            TextField textField = new TextField();
            Button button = new Button("show popup");
            button.setOnAction(e->{
                RegionUtil.FxmlBean<Pane, DemoPaneController> fxmlBean = RegionUtil.loadFxmlBean(this.getClass().getResource("/demo/DemoPane.fxml"));
                fxmlBean.getController().setName("demo ");

                Pane pane = fxmlBean.getNode();
                System.out.println("pane: " + pane.getClass().getName());
                pane.setStyle(textField.getText());
                System.out.println("style: " + pane.getStyle());

                popup.getContent().clear();
                popup.getContent().add(pane);
                double x = RegionUtil.calcAnchorX(button, PopupPos.down_east, button) + button.getWidth();
                double y = RegionUtil.calcAnchorY(button, PopupPos.down_east, button);
                popup.show(button.getScene().getWindow(), x, y);
            });

            vBox.getChildren().addAll(textField, button);
            return vBox;
        });

        addTest("test", "imTextPane", ()->{
            VBox vBox = new VBox();
            vBox.setStyle("-fx-background-color: white");

            TextField textField = new TextField();
            Button button = new Button("add imTextPane");
            button.setOnAction(e->{
                ImTextPane imTextPane = new ImTextPane(null);
                imTextPane.setImContent("asfsafadfaf");
                imTextPane.getStyleClass().add("im-text");
                vBox.getChildren().add(imTextPane);
            });

            Button button2 = new Button("add imText");
            button2.setOnAction(e->{
                ImText imTextPane = new ImText(null);
                imTextPane.getStyleClass().add("im-text");
                imTextPane.setImContent("asfsafadfaf");
                vBox.getChildren().add(imTextPane);
            });

            Button button3 = new Button("add imText");
            button3.setOnAction(e->{
                Text imTextPane = new Text();
                imTextPane.setText("asfsafadfaf");
                imTextPane.setStyle("-fx-fill: red; -fx-font-size: 26px;");
                vBox.getChildren().add(new Group(imTextPane));
            });

            vBox.getChildren().addAll(textField, button, button2, button3);
            return vBox;
        });

        addTest("test", "dialog show success", ()->{
            StackPane root = new StackPane();
            VBox vBox = new VBox();
            vBox.setStyle("-fx-background-color: white");
            Popup popup = new Popup();
            popup.setAutoHide(true);

            Button button = new Button("show dialog success");
            button.setOnAction(e->{
                DialogUtil.showSuccess(root, "保存成功");
            });

            Button button1 = new Button("show dialog error");
            button1.setOnAction(e->{
                DialogUtil.showError(root, "保存数据失败，请联系管理员");
            });

            Button button2 = new Button("show dialog");
            button2.setOnAction(e->{
                DialogUtil.dialog(root, "保存数据失败，请联系管理员", "测试！！！！！！！！！！！！！");
            });

            vBox.getChildren().addAll(button, button1, button2);

            root.getChildren().add(vBox);
            return root;
        });

        addTest("test", "new Stage test", ()->{
            StackPane root = new StackPane();
            VBox vBox = new VBox();

            Button button = new Button("show dialog success");
            button.setOnAction(ee->{
                Stage newStage = new Stage();
                HBox hBox = new HBox();
                Button b1 = new Button("close");
                b1.setOnAction(e->{
                    newStage.close();
                });
                hBox.getChildren().add(b1);
                Scene scene = new Scene(hBox, 300, 500);
                newStage.setScene(scene);
                newStage.show();
            });

            vBox.getChildren().addAll(button);

            root.getChildren().add(vBox);
            return root;
        });

        addTest("test", "Shape subtract", ()->{
            StackPane root = new StackPane();
            VBox vBox = new VBox();

            Rectangle rect = new Rectangle(10, 10, 100, 100);
            rect.setFill(Color.BLUE);
            Circle circle = new Circle(60, 60, 20);
            circle.setFill(Color.WHITE);
            Shape clip = Shape.subtract(rect, circle); // 使用Shape的subtract方法来创建一个遮罩
            clip.setFill(Color.LIGHTGRAY);

            vBox.getChildren().addAll(clip);

            root.getChildren().add(vBox);
            return root;
        });
    }

    private Node buildTop() {
        HBox hBox = new HBox();
        hBox.getStyleClass().add("demo-header");
        Label title = new Label("javafx 测试平台");
        hBox.getChildren().add(title);
        return hBox;
    }

    private void addTest(boolean isShow, String title, Supplier<Node> supplier) {
        String group = isShow ? "" : "隐藏";
        addTest(group, title, supplier);
    }

    private void addTest(String title, Supplier<Node> supplier) {
        addTest("", title, supplier);
    }

    private void addTest(String group, String title, Supplier<Node> supplier) {
        try {
            Button jfxButton = new Button(title);

            jfxButton.setOnAction(e->{
                Node node = null; //cacheMap.get(title);
                if (node == null) {
                    node = supplier.get();
                }
                if (node == null) return;

                cacheMap.putIfAbsent(title, node);
                contentPane.getChildren().clear();
                contentPane.getChildren().add(node);
            });

            if (group.equals("")) {
                leftPane.getChildren().add(jfxButton);
                return;
            }
            TitledPane titledPane = groupMap.get(group);
            if (titledPane == null) {
                VBox vBox = new VBox(5);
                titledPane = new TitledPane(group, vBox);
                titledPane.setExpanded(false);
                leftPane.getChildren().add(titledPane);
                groupMap.put(group, titledPane);
            }
            VBox box = (VBox) titledPane.getContent();
            box.getChildren().add(jfxButton);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Callback<Activity, Node> listCell(Image image) {
        Callback<Activity, Node> callback = activity->{
//            System.out.println("new node:" + activity.getId());
            GridPane gridPane = new GridPane();
            gridPane.getStyleClass().add("list-node");

            CheckBox checkBox = new CheckBox();
            checkBox.selectedProperty().bindBidirectional(activity.selectProperty);

            ImageView imageView = new ImageView(image);
            imageView.setFitHeight(50);
            imageView.setFitWidth(50);
            Label title = new Label(activity.getTitle());
            title.getStyleClass().add("title-label");
            ImText subTitle = new ImText(TextAlignment.LEFT);
            subTitle.setImContent(activity.getSubTitle());
//            subTitle.setMaxWidth(300);
            if (StageUtil.calcWidth(activity.getSubTitle()) >= 300) {
                subTitle.getText().setWrappingWidth(300);
            }
            subTitle.getStyleClass().add("subtitle-label");
            gridPane.getChildren().addAll(checkBox, imageView, title, subTitle);
            int row =0, col=0;
            GridPane.setConstraints(checkBox, col++, row, 1, 2);
            GridPane.setConstraints(imageView, col++, row, 1, 2);
            GridPane.setConstraints(title, col, row++, 2, 1);
            GridPane.setConstraints(subTitle, col, row, 1, 1);

            return gridPane;
        };

        return callback;
    }

    private Callback<Activity, Node> listCell(BooleanProperty booleanProperty) {
        Callback<Activity, Node> callback = activity->{
            GridPane gridPane = new GridPane();
            gridPane.getStyleClass().add("list-node");

            CheckBox checkBox = new CheckBox();
            checkBox.selectedProperty().bindBidirectional(activity.selectProperty);

            ImageView imageView = new ImageView(new Image(this.getClass().getResource("/imgs/head.jpg").toExternalForm()));
            imageView.setFitHeight(50);
            imageView.setFitWidth(50);

            Label title = new Label(activity.getTitle());
            title.getStyleClass().add("title-label");
            ImText subTitle = new ImText(TextAlignment.LEFT);
            subTitle.setImContent(activity.getSubTitle());
//            subTitle.setMaxWidth(300);
            if (StageUtil.calcWidth(activity.getSubTitle()) >= 300) {
                subTitle.getText().setWrappingWidth(300);
            }
            subTitle.getStyleClass().add("subtitle-label");
            gridPane.getChildren().addAll(checkBox, imageView, title, subTitle);
            int row =0, col=0;
            GridPane.setConstraints(checkBox, col++, row, 1, 2);
            GridPane.setConstraints(imageView, col++, row, 1, 2);
            GridPane.setConstraints(title, col, row++, 2, 1);
            GridPane.setConstraints(subTitle, col, row, 1, 1);

            checkBox.visibleProperty().bind(booleanProperty);
            checkBox.managedProperty().bind(booleanProperty);
            return gridPane;
        };
//        activityListView.setCellCallback(callback);

        return callback;
    }

    private List<Activity> loadActivityList(int num) {
        List<Activity> activities = new ArrayList();
        for(int i=0; i<num; i++) {
            Activity activity = createActivity(i);
            activities.add(activity);
        }
        return activities;
    }

    private Activity createActivity(int num) {
        Activity activity = new Activity();
        activity.setId(num+1);
        activity.setIconDescription(FontAwesome.SEARCH.getDescription());
        activity.setTitle("demo " + num);
        activity.setSubTitle("sub demo "+ num + randomString());
        activity.setLocalDateTime(LocalDateTime.now());
        return activity;
    }

    private List<WrapImMessage> loadImMessageList(int num) {
        List<WrapImMessage> messages = new ArrayList();
        for(int i=0; i<num; i++) {
            ImMessage message = createImMessage(i);
            messages.add(WrapImMessage.wrap(message));
        }
        return messages;
    }

    private ImMessage createImMessage(int num) {
        ImMessage message = new ImMessage();
        message.setId((long)(num+1));
        message.setServerId(message.getId() + "");
        message.setMessageType(MessageType.Text);
        message.setContent("hello world " + num);
        message.setFromUserId("test00100");
        message.setServerId(message.getId() + "");
        message.setSessionId(1L);
        message.setStatus(ImMessageStatus.sended);
        message.setActionStatus(ImMessageActionStatus.readed);
        message.setCreateDateTime(DateUtil.toDate(now.plusSeconds(-(num-10000))));
        message.setServerReceiveTime(DateUtil.toDate(now.plusSeconds((num-10000))));
        return message;
    }

    private String randomString() {
        int n = (int)(Math.random() * 100);
        StringBuilder sb = new StringBuilder();
        for(int i=0; i<n; i++) {
            sb.append(i);
        }
        return sb.toString();
    }


    private class Activity implements SoftId {
        private Integer id;
        private String title;
        private String subTitle;
        private LocalDateTime localDateTime;
        private BooleanProperty selectProperty = new SimpleBooleanProperty(false);

        private String iconDescription;
        private String pictureUrl;

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getSubTitle() {
            return subTitle;
        }

        public void setSubTitle(String subTitle) {
            this.subTitle = subTitle;
        }

        public LocalDateTime getLocalDateTime() {
            return localDateTime;
        }

        public void setLocalDateTime(LocalDateTime localDateTime) {
            this.localDateTime = localDateTime;
        }

        public String getIconDescription() {
            return iconDescription;
        }

        public void setIconDescription(String iconDescription) {
            this.iconDescription = iconDescription;
        }

        public String getPictureUrl() {
            return pictureUrl;
        }

        public void setPictureUrl(String pictureUrl) {
            this.pictureUrl = pictureUrl;
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }


        @Override
        public String getSoftId() {
            return id + "";
        }

        public boolean isSelectProperty() {
            return selectProperty.get();
        }

        public BooleanProperty selectPropertyProperty() {
            return selectProperty;
        }

        public void setSelectProperty(boolean selectProperty) {
            this.selectProperty.set(selectProperty);
        }
    }

    private void cleanWeakMap() {
        List<Integer> ids = new ArrayList<>();
        weakMap.forEach((k, v)->{
            if(v == null) ids.add(k);
        });
        ids.forEach(key->weakMap.remove(key));
    }

    private Text buildText(Text tmp) {
        Text t = new Text();
        double w =  0;
        double h =  0;


        return t;
    }

    private void addExtraCaret(InlineCssTextArea area) {
        CaretNode extraCaret = new CaretNode("another caret", area);
        if (!area.addCaret(extraCaret)) {
            throw new IllegalStateException("caret was not added to area");
        }

        int t = area.getContent().getParagraphs().size();
        int c = area.getContent().getParagraph(t-1).length();
        extraCaret.moveTo(t-1, c);

        // since the CSS properties are re-set when it applies the CSS from files
        // remove the style class so that properties set below are not overridden by CSS
        extraCaret.getStyleClass().remove("caret");

        extraCaret.setStrokeWidth(10.0);
        extraCaret.setStroke(Color.BROWN);
        extraCaret.setBlinkRate(Duration.millis(200));
    }

    private void addExtraSelection(InlineCssTextArea area) {
        Selection<String, String, String> extraSelection = new SelectionImpl<>("another selection", area,
                path -> {
                    // make rendered selection path look like a yellow highlighter
                    path.setStrokeWidth(0);
                    path.setFill(Color.YELLOW);
                }
        );
        if (!area.addSelection(extraSelection)) {
            throw new IllegalStateException("selection was not added to area");
        }

        // select something so it is visible
        extraSelection.selectRange(7, 2, 7, 8);
    }

    /**
     * Shows that RTFX-specific-CSS shapes are laid out in correct order, so that
     * selection and text and caret appears on top of them when made/moved
     */
    private void setupRTFXSpecificCSSShapes(InlineCssTextArea area) {
        String background = "-rtfx-background-color: red; ";
        String underline = "-rtfx-underline-color: blue; " +
                "-rtfx-underline-width: 2.0;";
        String border = "-rtfx-border-stroke-color: green; " +
                "-rtfx-border-stroke-width: 3.0;";

        // set all of them at once on a give line to insure they display properly
        area.setStyle(0, background + underline + border);

        // set each one over a given segment
        area.setStyle(1, 0, 3, background);
        area.setStyle(1, 4, 7, underline);
        area.setStyle(1, 8, 11, border);
    }

    private void addItems(ObservableList<Label> items, Ikon[] devicons, Function<String, Ikon> function) {
        for(Ikon devicon : devicons) {
            FontIcon fontIcon1 = FontIcon.of(function.apply(devicon.getDescription()), 20, Color.BLACK);

            Label label = new Label();
            label.setGraphic(fontIcon1);
            label.setText(fontIcon1.getIconLiteral());
            label.setContentDisplay(ContentDisplay.TOP);
            label.setOnMouseClicked(e->{
                System.out.println("fontIcon:" + devicon.getDescription());
            });
            items.add(label);
        }
    }

    private void sleep(int time) {
        try {
            Thread.sleep(time);
        } catch (Exception e){}
    }
}
